
// 和网络 中 接受连接【accept】 有关的函数放这里

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>   //uintptr_t
#include <stdarg.h>   //va_start....
#include <unistd.h>   //STDERR_FILENO等
#include <sys/time.h> //gettimeofday
#include <time.h>     //localtime_r
#include <fcntl.h>    //open
#include <errno.h>    //errno
// #include <sys/socket.h>
#include <sys/ioctl.h> //ioctl
#include <arpa/inet.h>

#include "ngx_c_conf.h"
#include "ngx_macro.h"
#include "ngx_global.h"
#include "ngx_func.h"
#include "ngx_c_socket.h"

// 建立新连接专用函数，当新连接进入时，本函数会被ngx_epoll_process_events()所调用
void CSocekt::ngx_event_accept(lpngx_connection_t oldc)
{
    struct sockaddr mysockaddr; // 远端服务器的socket地址
    socklen_t socklen;
    int err;
    int level;
    int s;
    static int use_accept4 = 1; // 我们先认为能够使用accept4()函数
    lpngx_connection_t newc;    // 代表连接池中的一个连接【注意这是指针】

    //ngx_log_stderr(0,"这是几个\n"); //惊群测试

    socklen = sizeof(mysockaddr);
    do
    {
        if (use_accept4)
        {
            s = accept4(oldc->fd, &mysockaddr, &socklen, SOCK_NONBLOCK);
        }
        else
        {
            s = accept(oldc->fd, &mysockaddr, &socklen);
        }

        if (s == -1)
        {
            err = errno;
            if (err = EAGAIN) // accept没有准备好
            {
                return;
            }
            level = NGX_LOG_ALERT;
            level = NGX_LOG_ALERT;
            if (err == ECONNABORTED) // ECONNRESET错误则发生在对方意外关闭套接字后【您的主机中的软件放弃了一个已建立的连接--由于超时或者其它失败而中止接连(用户插拔网线就可能有这个错误出现)】
            {
                // 该错误被描述为“software caused connection abort”，即“软件引起的连接中止”。原因在于当服务和客户进程在完成用于 TCP 连接的“三次握手”后，
                // 客户 TCP 却发送了一个 RST （复位）分节，在服务进程看来，就在该连接已由 TCP 排队，等着服务进程调用 accept 的时候 RST 却到达了。
                // POSIX 规定此时的 errno 值必须 ECONNABORTED。源自 Berkeley 的实现完全在内核中处理中止的连接，服务进程将永远不知道该中止的发生。
                // 服务器进程一般可以忽略该错误，直接再次调用accept。
                level = NGX_LOG_ERR;
            }
            else if (err == EMFILE || err == ENFILE) // EMFILE:进程的fd已用尽
            {
                level = NGX_LOG_CRIT;
            }

            ngx_log_error_core(level, errno, "CSocekt::ngx_event_accept()中accept4()失败!");

            if (use_accept4 && err == ENOSYS) // accept4()函数没实现
            {
                use_accept4 = 0; // 标记不使用accept4()函数，改用accept()函数
                continue;        // 回去重新用accept()函数搞
            }
            return;
        }

        // 走到这里表示accept成功
        // 加入连接池
        newc = ngx_get_connection(s);
        if (newc == NULL)
        {
            // 连接池中连接不够用，那么就得把这个socekt直接关闭并返回了，因为在ngx_get_connection()中已经写日志了，所以这里不需要写日志了
            if (close(s) == -1)
            {
                ngx_log_error_core(NGX_LOG_ALERT, errno, "CSocekt::ngx_event_accept()中close(%d)失败!", s);
            }
            return;
        }
        memcpy(&newc->s_sockaddr, &mysockaddr, socklen);

        if (!use_accept4)
        {
            // 如果不是用accept4()取得的socket，那么就要设置为非阻塞【因为用accept4()的已经被accept4()设置为非阻塞了】
            if (setnonblocking(s) == false)
            {
                // 设置非阻塞居然失败
                ngx_close_connection(newc);
                return; // 直接返回
            }
        }

        newc->listening = oldc->listening;
        newc->w_ready = 1;                                   // 标记可以写，新连接写事件肯定是ready的
        newc->rhandler = &CSocekt::ngx_wait_request_handler; // 设置数据来时的读处理函数

        // 这里将读事件加入epoll监控
        if (ngx_epoll_add_event(s,             // socket句柄
                                1, 0,          // 读，写
                                EPOLLET,       // 其他补充标记【EPOLLET(高速模式，边缘触发ET)】
                                EPOLL_CTL_ADD, // 事件类型【增加，还有删除/修改】
                                newc           // 连接池中的连接
                                ) == -1)
        {
            // 增加事件失败，失败日志在ngx_epoll_add_event中写过了，因此这里不多写啥；
            ngx_close_connection(newc);
            return; // 直接返回
        }

        break;
    } while (true);
    return;
}


